﻿//
// This file is part of AVSReflector project
// Copyright (C) 2011 Yuan Pengfei
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace AVSReflector.Core
{
    static class Decoder
    {
        public static uint ExpGolomb(BitStream s, int k = 0)
        {
            int zeros = -1;
            for (uint b = 0; b == 0; zeros++)
                b = s.Read(1);
            uint ret = (1u << (zeros + k)) - (1u << k) + s.Read(zeros + k);
            return ret;
        }

        public static int SignedExpGolomb(BitStream s)
        {
            int ret = 0;
            uint r = ExpGolomb(s);
            ret = (int)((r + 1) / 2);
            if(r % 2 == 0)
                ret = -ret;
            return ret;
        }

        static uint[] luti = null, luto = null;

        public static uint CBPLookup(uint codenum, bool intra)
        {
            if (luti == null)
                luti = new uint[64] {
                    63 , 15 , 31 , 47 , 0 , 14 , 13 , 11 , 
                    7 , 5 , 10 , 8 , 12 , 61 , 4 , 55 , 
                    1 , 2 , 59 , 3 , 62 , 9 , 6 , 29 , 
                    45 , 51 , 23 , 39 , 27 , 46 , 53 , 30 , 
                    43 , 37 , 60 , 16 , 21 , 28 , 19 , 35 , 
                    42 , 26 , 44 , 32 , 58 , 24 , 20 , 17 , 
                    18 , 48 , 22 , 33 , 25 , 49 , 40 , 36 , 
                    34 , 50 , 52 , 54 , 41 , 56 , 38 , 57};
            if (luto == null)
                luto = new uint[64] {
                    0 , 15 , 63 , 31 , 16 , 32 , 47 , 13 , 
                    14 , 11 , 12 , 5 , 10 , 7 , 48 , 3 , 
                    2 , 8 , 4 , 1 , 61 , 55 , 59 , 62 , 
                    29 , 27 , 23 , 19 , 30 , 28 , 9 , 6 , 
                    60 , 21 , 44 , 26 , 51 , 35 , 18 , 20 , 
                    24 , 53 , 17 , 37 , 39 , 45 , 58 , 43 , 
                    42 , 46 , 36 , 33 , 34 , 40 , 52 , 49 , 
                    50 , 56 , 25 , 22 , 54 , 57 , 41 , 38};

            if (intra) return luti[codenum];
            else return luto[codenum];
        }

        public static uint CPB422Lookup(uint codenum)
        {
            return codenum;
        }

        static int[,] scana, scanb;

        public static void Rscan(int[] array, int[,] matrix)
        {
            if (scana == null)
            {
                scana = new int[8, 8] {
                    {0 ,2 ,3 ,9 ,10,20,21,35},
                    {1 ,4 ,8 ,11,19,22,34,36},
                    {5 ,7 ,12,18,23,33,37,48},
                    {6 ,13,17,24,32,38,47,49},
                    {14,16,25,31,39,46,50,57},
                    {15,26,30,40,45,51,56,58},
                    {27,29,41,44,52,55,59,62},
                    {28,42,43,53,54,60,61,63}
                };
            }
            if (scanb == null)
            {
                scanb = new int[8, 8] {
                    {0 ,1 ,2 ,4 ,5 ,8 ,9 ,13},
                    {3 ,6 ,7 ,10,14,17,18,23},
                    {11,12,15,19,24,26,29,34},
                    {16,20,21,27,30,35,40,45},
                    {22,25,28,31,36,41,46,49},
                    {32,33,37,39,44,48,51,53},
                    {38,42,43,47,50,52,54,56},
                    {55,57,58,59,60,61,62,63}
                };
            }
            int[,] scan;
            if (Context.currentPicture.progressive_frame == 1 || Context.currentPicture.picture_structure == 1)
                scan = scana;
            else
                scan = scanb;
            for (int i = 0; i < 8; ++i)
                for (int j = 0; j < 8; ++j)
                    matrix[i, j] = array[scan[i, j]];
        }

        public static int ChromaQP(int n)
        {
            int ret = n;
            switch(n)
            {
                case 43:
                    ret = 42;
                    break;
                case 44:
                case 45:
                    ret = 43;
                    break;
                case 46:
                case 47:
                    ret = 44;
                    break;
                case 48:
                case 49:
                    ret = 45;
                    break;
                case 50:
                case 51:
                    ret = 46;
                    break;
                case 52:
                case 53:
                    ret = 47;
                    break;
                case 54:
                case 55:
                case 56:
                    ret = 48;
                    break;
                case 57:
                case 58:
                case 59:
                    ret = 49;
                    break;
                case 60:
                case 61:
                case 62:
                    ret = 50;
                    break;
                case 63:
                    ret = 51;
                    break;
            }
            return ret;
        }

        static int[] dequantTable;

        public static int DequantTable(int qp)
        {
            if (dequantTable == null)
            {
                dequantTable = new int[64] {
                    32768, 36061, 38968, 42495, 46341, 50535, 55437, 60424,
                    32932, 35734, 38968, 42495, 46177, 50535, 55109, 59933,
                    65535, 35734, 38968, 42577, 46341, 50617, 55027, 60097,
                    32809, 35734, 38968, 42454, 46382, 50576, 55109, 60056,
                    65535, 35734, 38968, 42495, 46320, 50515, 55109, 60076,
                    65535, 35744, 38968, 42495, 46341, 50535, 55099, 60087,
                    65535, 35734, 38973, 42500, 46341, 50535, 55109, 60097,
                    32771, 35734, 38965, 42497, 46341, 50535, 55109, 60099
                };
            }
            return dequantTable[qp];
        }

        static int[] shiftTable;

        public static int ShiftTable(int qp)
        {
            if (shiftTable == null)
            {
                shiftTable = new int[64] {
                    14, 14, 14, 14, 14, 14, 14, 14,
                    13, 13, 13, 13, 13, 13, 13, 13,
                    13, 12, 12, 12, 12, 12, 12, 12,
                    11, 11, 11, 11, 11, 11, 11, 11,
                    11, 10, 10, 10, 10, 10, 10, 10,
                    10, 9 , 9 , 9 , 9 , 9 , 9 , 9 ,
                    9 , 8 , 8 , 8 , 8 , 8 , 8 , 8 ,
                    7 , 7 , 7 , 7 , 7 , 7 , 7 , 7
                };
            }
            return shiftTable[qp];
        }

        static int[,] trans;

        public static void Atransform(int[,] coeff, int[,] residue)
        {
            if (trans == null)
            {
                trans = new int[8, 8]{
                    { 8 ,10 ,10 , 9 , 8 , 6 , 4 , 2 },
                    { 8 , 9 , 4 ,-2 ,-8 ,-10,-10,-6 },
                    { 8 , 6 ,-4 ,-10,-8 , 2 ,10 , 9 },
                    { 8 , 2 ,-10,-6 , 8 , 9 ,-4 ,-10},
                    { 8 ,-2 ,-10, 6 , 8 ,-9 ,-4 ,10 },
                    { 8 ,-6 ,-4 ,10 ,-8 ,-2 ,10 ,-9 },
                    { 8 ,-9 , 4 , 2 ,-8 ,10 ,-10, 6 },
                    { 8 ,-10,10 ,-9 , 8 ,-6 , 4 ,-2 }
                };
            }

            int[,] h = new int[8, 8];
            const int min = -(1 << 15), max = (1 << 15) - 1;

            for (int i = 0; i < 8; ++i)
                for (int j = 0; j < 8; ++j)
                {
                    int val = 0;
                    for (int k = 0; k < 8; ++k)
                        val += coeff[i, k] * trans[j, k];
                    h[i, j] = Clip3(min, max, val + 4) >> 3;
                }

            for (int i = 0; i < 8; ++i)
                for (int j = 0; j < 8; ++j)
                {
                    int val = 0;
                    for (int k = 0; k < 8; ++k)
                        val += trans[i, k] * h[k, j];
                    residue[i, j] = Clip3(min, max, val + (1 << 6)) >> 7;
                }
        }

        static int Clip3(int min, int max, int val)
        {
            int ret = val;
            if (ret < min) ret = min;
            if (ret > max) ret = max;
            return ret;
        }

        public static int Clip1(int val)
        {
            return Clip3(0, 255, val);
        }

        // TODO: Transform color according to sequence display extension
        public static void YCbCr2Color(int y, int cb, int cr, out byte r, out byte g, out byte b)
        {
            double er, eg, eb;
            double ey, epb, epr;
            ey = y / 255.0;
            epb = (cb - 128) / 255.0;
            epr = (cr - 128) / 255.0;
            er = ey + 1.575 * epr;
            eg = ey - 0.187 * epb - 0.468 * epr;
            eb = ey + 1.856 * epb;
            r = (byte)Clip1((int)(er * 255));
            g = (byte)Clip1((int)(eg * 255));
            b = (byte)Clip1((int)(eb * 255));
        }
    }
}
